#  sniffer.rb
#
#  This file is part of myvnet.
#
#  Copyright (C) 2008  Filipe David Borba Manana,  fdmanana@gmail.com
#
#  myvnet is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  myvnet is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with myvnet.  If not, see <http://www.gnu.org/licenses/>.


require 'pcaplet'
require 'lib/exceptions'
require 'lib/protocols/ethernet'


# Module for network traffic related operations. 
#
module Packet
  
  
  # Packet sniffer.
  #
  class Sniffer
    
    attr_reader :interface
    attr_reader :filter
  
    # === Synopsis
    #
    #   Packet::Sniffer.new(interface [, packet_count])
    #
    # === Args
    #
    # +interface+::
    #   Name of the network interface to use for packet sniffing.
    # +packet_count+::
    #   Maximum number of packets to capture. Defaults to unlimited (-1).
    #
    # === Exceptions
    #
    # +SnifferError+::
    #   If by some reason it was not possible to bind the packet sniffer to 
    #   the specified network interface in promiscuous mode.
    #
    # === Description
    #
    # Instantiates a network traffic sniffer.
    #
    def initialize(interface, packet_count = -1)
      @interface = interface
      @packet_count = packet_count
      @filter = nil
      
      begin
        @cap = Pcap::Capture.open_live(@interface, 1500);
      rescue Pcap::PcapError => ex
        raise SnifferError, ex.message
      end
    end
    
    
    # === Synopsis
    #
    #   Packet::Sniffer#add_filter(filter)
    #
    # === Args
    #
    # +filter+::
    #   A string specifying a _tcpdump_ network traffic filter.
    #   Check http://www.tcpdump.org for more details.
    #
    # === Return
    #
    # The complete network traffic filter after the given filter was 
    # added to the sniffer.
    #
    # === Description
    #
    # Adds a tcpdump-like filter to the sniffer.
    #
    def add_filter(filter)
      if @filter.nil?
        @filter = filter
      else
        @filter = "( #{@filter} ) or ( #{filter} )"
      end
      
      @cap.setfilter(@filter)      
      @filter
    end
    
    
    # === Synopsis
    #
    #   Packet::Sniffer#stop
    #
    # === Return
    #
    # No value.
    #
    # === Description
    #
    # Stops the sniffer from capturing network traffic.
    #
    def stop
      @cap.close
    end
    

    # === Synopsis
    #
    #   Packet::Sniffer#each_frame { |frame| ... }
    #
    # === Return
    #
    # No value.
    #
    # === Description
    #
    # Yields captured network traffic, as instances of the class Ethernet::Packet, 
    # to the given block. 
    #
    def each_frame(&block)
      
      @cap.each_packet(@packet_count) do |p|
        frame = Ethernet::Packet.new(p.raw_data)        
        block.call frame
      end
      
    end
        
    alias each_packet each_frame
    alias each each_frame
    
    
    # === Synopsis
    #
    #   Packet::Sniffer#received
    #
    # === Return
    #
    # Number of packets received by the sniffer.
    #
    # === Description
    #
    # Returns the number of packets received by the sniffer.  
    #
    def received
      @cap.stats.recv
    end
  

    # === Synopsis
    #
    #   Packet::Sniffer#dropped
    #
    # === Return
    #
    # Number of packets dropped by the sniffer.
    #
    # === Description
    #
    # Returns the number of packets dropped by the sniffer, that is, the number of
    # packets that did not match the sniffer's filter.  
    #
    def dropped
      @cap.stats.drop
    end
    
  end
  
end
